use proto::types::{ACK, PING, PONG};
use tokio::{io::AsyncWriteExt, net::tcp::OwnedWriteHalf, sync::mpsc};
use crate::{AppErr, ErrConv};

use super::proto::{ProtoWriter, ProtoWriter2};


enum Body {
    Ack(u8),
    Ping(u8),
    Pong(u8),
    Data(Box<[u8]>),
    Exit,
}

pub struct ConnWriter2 {
    tx: mpsc::Sender<Body>
}

impl ConnWriter2 {

    pub fn new(w: OwnedWriteHalf) -> ConnWriter2 {  
        let (tx, rx) = mpsc::channel(64);
        tokio::spawn( write_task(rx, w) );
        ConnWriter2 { tx }
    }

    pub async fn close(&self) -> Result<(), AppErr> {
        self.tx.send(Body::Exit).await.conv()?;
        Ok(())
    }

    pub fn ack(&self, seq: u8) -> Result<(), AppErr> {
        self.tx.try_send(Body::Ack(seq)).conv()?;
        Ok(())
    }

    pub fn ping(&self, seq: u8) -> Result<(), AppErr> {
        self.tx.try_send(Body::Ping(seq)).conv()?;
        Ok(())
    }

    pub fn pong(&self, seq: u8) -> Result<(), AppErr> {
        self.tx.try_send(Body::Pong(seq)).conv()?;
        Ok(())
    }
}

impl ProtoWriter2<AppErr> for ConnWriter2 {

    async fn write(&self, buf: Box<[u8]>) -> Result<(), AppErr> {
        self.tx.send(Body::Data(buf)).await.conv()?;
        Ok(())
    }
}

async fn write_loop(rx: &mut mpsc::Receiver<Body>, w: &mut OwnedWriteHalf) -> Result<(), AppErr> {

    while let Some(body) = rx.recv().await {
        match body {
            Body::Ack(seq) => w.write_min_frame(ACK, seq).await?,
            Body::Ping(seq) => w.write_min_frame(PING, seq).await?,
            Body::Pong(seq) => w.write_min_frame(PONG, seq).await?,
            Body::Data(buf) => w.write_all(&buf).await?,
            Body::Exit => return Ok(())
        };
    }

    Ok(())
}

async fn write_task(mut rx: mpsc::Receiver<Body>, mut w: OwnedWriteHalf) {

    if let Err(e) = write_loop(&mut rx, &mut w).await {
        println!("write err:{}", e);
    }
    println!("write exit");
}


